跳到主要内容

SpringMVC 拦截器与过滤器

Filter 过滤器

参考资料 springboot-guide

Filter 过滤器主要是用来过滤用户请求的,它允许我们对用户请求进行前置处理和后置处理,比如实现 URL 级别的权限控制、过滤非法请求等等。Filter 过滤器是面向切面编程——AOP 的具体实现(AOP切面编程只是一种编程思想而已)。

另外,Filter 是依赖于 Servlet 容器,Filter 接口就在 Servlet 包下面,属于 Servlet 规范的一部分。

如果我们需要自定义 Filter 的话非常简单,只需要实现 javax.Servlet.Filter 接口,然后重写里面的 3 个方法即可!

public interface Filter {

//初始化过滤器后执行的操作
default void init(FilterConfig filterConfig) throws ServletException {
}

// 对请求进行过滤
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;

// 销毁过滤器后执行的操作,主要用户对某些资源的回收
default void destroy() {
}
}

Filter 是如何实现拦截的?

Filter接口中有一个叫做 doFilter 的方法,这个方法实现了对用户请求的过滤。具体流程大体是这样的:

  1. 用户发送请求到 web 服务器,请求会先到过滤器;
  2. 过滤器会对请求进行一些处理比如过滤请求的参数、修改返回给客户端的 response 的内容、判断是否让用户访问该接口等等。
  3. 用户请求响应完毕。
  4. 进行一些自己想要的其他操作。

自定义过滤器

例如这里自定义一个 JWT 过滤器

@Slf4j
public class JwtFilter implements Filter {

@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;

response.setContentType("application/json; charset=UTF-8");
//获取 header里的token
final String token = request.getHeader("authorization");

if (token == null) {
response.getWriter().write("no token");
return;
}

DecodedJWT verifier = JWTUtils.verifier(token);

if (verifier == null) {
response.getWriter().write("authentication failure!!");
return;
}

Claim username = verifier.getClaims().get("username");
request.setAttribute("userName", username);
chain.doFilter(req, res);
}
}

在 XML 注册

然后在 web.xml 里面注册这个 Filter

注意字符编码过滤器要使用 /** 来处理所有的请求

<filter>
<filter-name>JwtFilter</filter-name>
<filter-class>com.alsritter.filters.JwtFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>JwtFilter</filter-name>
<url-pattern>/**</url-pattern>
</filter-mapping>

配置中注册自定义的过滤器

@Configuration
public class MyFilterConfig {
@Autowired
JwtFilter myFilter;

@Bean
public FilterRegistrationBean<JwtFilter> thirdFilter() {
FilterRegistrationBean<JwtFilter> filterRegistrationBean = new FilterRegistrationBean<>();

filterRegistrationBean.setFilter(myFilter);

filterRegistrationBean.setUrlPatterns(new ArrayList<>(Arrays.asList("/**")));

return filterRegistrationBean;
}
}

通过提供好的一些注解实现

在自己的过滤器的类上加上 @WebFilter 然后在这个注解中通过它提供好的一些参数进行配置。

使用这个注解之后就可以直接编写过滤器并直接注册了

@WebFilter(filterName = "MyFilterWithAnnotation", urlPatterns = "/**")
public class MyFilterWithAnnotation implements Filter {

......
}

另外,为了能让 Spring 找到它,需要在启动类上加上 @ServletComponentScan 注解。

决定过滤器执行顺序

定义多个拦截器,并决定它们的执行顺序

在配置中注册自定义的过滤器,通过 FilterRegistrationBean 的 setOrder 方法可以决定 Filter 的执行顺序。

@Configuration
public class MyFilterConfig {
@Autowired
MyFilter myFilter;

@Autowired
MyFilter2 myFilter2;

@Bean
public FilterRegistrationBean<MyFilter> setUpMyFilter() {
FilterRegistrationBean<MyFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setOrder(2);
filterRegistrationBean.setFilter(myFilter);
filterRegistrationBean.setUrlPatterns(new ArrayList<>(Arrays.asList("/api/*")));

return filterRegistrationBean;
}

@Bean
public FilterRegistrationBean<MyFilter2> setUpMyFilter2() {
FilterRegistrationBean<MyFilter2> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setOrder(1);
filterRegistrationBean.setFilter(myFilter2);
filterRegistrationBean.setUrlPatterns(new ArrayList<>(Arrays.asList("/api/*")));
return filterRegistrationBean;
}
}

Interceptor 拦截器

拦截器(Interceptor)同 Filter 过滤器一样,它俩都是面向切面编程——AOP 的具体实现,可以使用 Interceptor 来执行某些任务,例如在 Controller 处理请求之前编写日志,添加或更新配置......

在 Spring中,当请求发送到 Controller 时,在被Controller处理之前,它必须经过 Interceptors(0或更多)

拦截器是如何工作的

自定义的 Interceptor 类要实现了 Spring 的 HandlerInterceptor 接口。

在继续看之前,先来看下拦截器执行流程图,以此对整个工作方式有个了解(注意,这里是多个拦截器):

继承实现了 HandlerInterceptor 接口的类,比如 Spring 已经提供的实现了 HandlerInterceptor 接口的抽象类 HandlerInterceptorAdapter。

HandlerInterceptor 接口中定义了三个方法,我们就是通过这三个方法来对用户的请求进行拦截处理的。

public class LoginInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {}
}

1、preHandle 方法:

这个方法在 Controller 处理请求之前被调用,SpringMVC 中的 Interceptor 是链式的调用的,在一个应用中或者说是在一个请求中可以同时存在多个Interceptor。

每个 Interceptor 的调用会依据它的声明顺序依次执行,而且最先执行的都是 Interceptor 中的 preHandle 方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。

该方法的返回值是布尔值 Boolean 类型的,当它返回为 false 时,表示请求结束,后续的 Interceptor 和 Controller 都不会再执行;当返回值为 true 时就会继续调用下一个 Interceptor 的 preHandle 方法,如果已经是最后一个 Interceptor 的时候就会是调用当前请求的 Controller 方法。

2、postHandle 方法

这个方法在 Controller 方法处理当前请求之后执行,但是它会在 DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对 Controller 处理之后的 ModelAndView 对象进行操作。

postHandle 方法被调用的方向跟 preHandle 是相反的,也就是说先声明的 Interceptor 的 postHandle 方法反而会后执行。

3、afterCompletion 方法

这个方法也是需要当前对应的 Interceptor 的 preHandle 方法的返回值为 true 时才会执行。

顾名思义,该方法将在整个请求结束之后,也就是在 DispatcherServlet 渲染了对应的视图之后执行。这个方法的主要作用是用于进行资源清理工作的。

自定义拦截器

public class MyInterceptor implements HandlerInterceptor {

//return true 放行,执行下一个拦截器
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("====访问方法前执行====");
return true;
}

//可以这里写访问日志
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("====访问方法后执行====");
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("====呈现视图后执行,一般用于释放资源====");
}
}

再在 SpringConfig 里配置这个拦截器

<mvc:interceptors>
<mvc:interceptor>
<!-- /** 表示这个请求下的所有请求 例如/test/t1
一个* 表示只匹配一级请求-->
<mvc:mapping path="/ajax/**"/>
<bean class="com.alsritter.config.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>

使用拦截器重定向

OldLoginInterceptor 是一个拦截器,如果用户输入已经被废弃的链接 /admin/oldLogin,它将重定向到新的 /admin/login

public class OldLoginInterceptor extends HandlerInterceptorAdapter {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {

System.out.println("\n-------- OldLoginInterceptor.preHandle --- ");
System.out.println("Request URL: " + request.getRequestURL());
System.out.println("Sorry! This URL is no longer used, Redirect to /admin/login");

response.sendRedirect(request.getContextPath() + "/admin/login");
return false;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, //
Object handler, ModelAndView modelAndView) throws Exception {

// This code will never be run.
System.out.println("\n-------- OldLoginInterceptor.postHandle --- ");
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, //
Object handler, Exception ex) throws Exception {

// This code will never be run.
System.out.println("\n-------- QueryStringInterceptor.afterCompletion --- ");
}

}

拦截器使用示例

例如做个拦截器用来取得请求

/**
* 请求拦截器,用来在请求到达之前先取得当前请求的信息
*
* @author alsritter
* @version 1.0
**/
public class RequestInteraction implements HandlerInterceptor {
/**
* 请求到来之前
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
ServletUtils.setRequest(request);
// 放行
return true;
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
ServletUtils.remove();
}
}

这里就可以直接创建一个工具类直接取得这个请求数据了

/**
* 用于取得当前请求的参数
*
* @author alsritter
* @version 1.0
**/
public final class ServletUtils {
private ServletUtils() {}

private static final ThreadLocal<HttpServletRequest> REQUEST = new ThreadLocal<>();

public static String getRequestURI() {
HttpServletRequest httpServletRequest = REQUEST.get();
return httpServletRequest.getRequestURI();
}

public static String getHeaderIgnoreCase(String header) {
HttpServletRequest httpServletRequest = REQUEST.get();
return httpServletRequest.getHeader(header.toLowerCase());
}

public static String getRequestIp() {
HttpServletRequest request = REQUEST.get();
String unknown = "unknown";
// 优先取 X-Real-IP
String ip = request.getHeader("X-Real-IP");
if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
ip = request.getHeader("x-forwarded-for");
}

if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
if ("0:0:0:0:0:0:0:1".equals(ip)) {
ip = "ERROR_IP";
}
}
if (unknown.equalsIgnoreCase(ip)) {
ip = "ERROR_IP";
return ip;
}
int index = ip.indexOf(',');
if (index >= 0) {
ip = ip.substring(0, index);
}

return ip;
}

public static void setRequest(HttpServletRequest request) {
REQUEST.set(request);
}

/**
* 别忘了用完要手动回收这个 ThreadLocal 变量,否则会内存泄漏
*/
public static void remove() {
REQUEST.remove();
}
}

自定义注解进行拦截

定义注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthToken {
}

编写拦截器

@Slf4j
public class AuthorizationTokenInterceptor implements HandlerInterceptor {


@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 第三个参数 handler 可以获取访问的目标对象(Controller)所以通过其能够得到目标对象上面是否存在自定义的注解

// 这个 HandlerMethod 可以用来匹配 Controller,如果不是 Controller 则跳过
if (!(handler instanceof HandlerMethod)) {
return true;
}

HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();

// 如果打上了 AuthToken 注解则需要验证 token
if (method.getAnnotation(AuthToken.class) != null ||
handlerMethod.getBeanType().getAnnotation(AuthToken.class) != null) {
// do something
}

// 其它的放行
return true;
}
}

过滤器和拦截器的区别

注意不要给名字误导了,实际上这两个都是设计模式中的拦截器模式,只是它们的名字有点不同而已 ~

这里再补充一下 Spring 的拦截器和 Servlet 的过滤器的区别

过滤器 和 拦截器 均体现了 AOP 的编程思想,都可以实现诸如日志记录、登录鉴权等功能,但二者的不同点也是比较多的,接下来一一说明。

过滤器的实现原理

过滤器和拦截器 底层实现方式大不相同,过滤器 是基于函数回调的,拦截器 则是基于 Java的反射机制(动态代理)实现的。

在我们自定义的过滤器中都会实现一个 doFilter() 方法,这个方法有一个 FilterChain 参数:

@Override
public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain)
throws IOException, ServletException {
// do something
// 把请求传回过滤链
chain.doFilter(request, response);
}

而实际上它是一个回调接口。ApplicationFilterChain 是它的实现类, 这个实现类内部也有一个 doFilter() 方法就是回调方法。

public interface FilterChain {
void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}

ApplicationFilterChain 里面能拿到我们自定义的 xxxFilter 类,在其内部回调方法 doFilter() 里调用各个自定义 xxxFilter 过滤器,并执行 doFilter() 方法。

public final class ApplicationFilterChain implements FilterChain {
@Override
public void doFilter(ServletRequest request, ServletResponse response) {
...//省略
internalDoFilter(request,response);
}

private void internalDoFilter(ServletRequest request, ServletResponse response){
if (pos < n) {
//获取第pos个filter
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
...
filter.doFilter(request, response, this);
}
}

}

而每个 xxxFilter 会先执行自身的 doFilter() 过滤逻辑,最后在执行结束前会执行 filterChain.doFilter(servletRequest, servletResponse),也就是回调 ApplicationFilterChain的doFilter() 方法,以此循环执行实现函数回调。

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException,ServletException {
filterChain.doFilter(servletRequest, servletResponse);
}

拦截器的实现原理

自定义的 Interceptor 类要实现了 Spring 的 HandlerInterceptor 接口。

在继续看之前,先来看下拦截器执行流程图,以此对整个工作方式有个了解(注意,这里是多个拦截器):

继承实现了 HandlerInterceptor 接口的类,比如 Spring 已经提供的实现了 HandlerInterceptor 接口的抽象类 HandlerInterceptorAdapter。

HandlerInterceptor 接口中定义了三个方法,我们就是通过这三个方法来对用户的请求进行拦截处理的。

public class LoginInterceptor implements HandlerInterceptor {

// Controller方法处理请求前执行,根据拦截器定义的顺序,正向执行。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
return true;
}

// Controller方法处理请求后执行,根据拦截器定义的顺序,逆向执行。
// 需要所有的 preHandle 方法都返回 true 时才会调用。
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {}

// View视图渲染后处理方法:根据拦截器定义的顺序,逆向执行。
// preHandle返回true就会调用。
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {}
}

使用范围不同

我们看到过滤器 实现的是 javax.servlet.Filter 接口,而这个接口是在 Servlet 规范中定义的,也就是说过滤器 Filter 的使用要依赖于 Tomcat 等容器,导致它只能在 web程序中使用。

而拦截器(Interceptor) 它是一个 Spring 组件,并由 Spring 容器管理,并不依赖 Tomcat 等容器,是可以单独使用的。不仅能应用在 web 程序中,也可以用于 Application、Swing 等程序中。

触发时机不同

过滤器 和 拦截器的触发时机也不同,我们看下边这张图。

过滤器 Filter 是在请求进入容器后,但在进入 servlet 之前进行预处理,请求结束是在 servlet 处理完以后。

拦截器 Interceptor 是在请求进入 servlet 后,在进入 Controller 之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。

Reference

参考资料 过滤器 和 拦截器 6个区别,别再傻傻分不清了